package com.lipaluma.test.db.internal;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import com.lipaluma.test.db.annotations.dataSource.DataSource;
import com.lipaluma.test.db.annotations.dataSource.Dialect;
import com.lipaluma.test.db.exceptions.DBTestIllegalUseException;

/**
 * Utilitary class that manages DataSources
 * @author Mario
 *
 */
public class DbUtils {
	
	private static Map<String, DialectEnum> mapDialectByDataSourceDeclaredInProperties = new HashMap<String, DialectEnum>();
	
	/**
	 * get the default DataSource
	 * @return the DataSource
	 * @throws IOException
	 */
	public static javax.sql.DataSource getDataSource() throws IOException {
		List<DataSource> datasources = new ArrayList<DataSource>();
		if(Context.getTestedClass().isAnnotationPresent(DataSource.List.class)) {
			datasources.addAll(Arrays.asList(Context.getTestedClass().getAnnotation(DataSource.List.class).value()));
		}
		if(Context.getTestedClass().isAnnotationPresent(DataSource.class)) {
			datasources.add(Context.getTestedClass().getAnnotation(DataSource.class));
		}
		if(!datasources.isEmpty()) {
			return getDefaultDataSourceByAnnotation(datasources);
		} else {
			try {
				return getDefaultDataSourceInContext();
			} catch (NoSuchBeanDefinitionException e) {
				return getDataSourceByProperties();
			}
		}
	}

	private static javax.sql.DataSource getDefaultDataSourceByAnnotation(List<DataSource> datasources) {
		for (DataSource dataSource : datasources) {
			if(dataSource.name().isEmpty())
				return getDataSourceByAnnotation(dataSource);
		}
		throw new DBTestIllegalUseException("There is no default Data source setted. To have Default data source, don't put a name.");
	}

	/**
	 * get DataSource named name
	 * @param name name of the datasource
	 * @return DataSource
	 * @throws IOException
	 */
	public static javax.sql.DataSource getDataSourceWithName(String name) throws IOException {
		List<DataSource> datasources = new ArrayList<DataSource>();
		if(Context.getTestedClass().isAnnotationPresent(DataSource.List.class)) {
			datasources.addAll(Arrays.asList(Context.getTestedClass().getAnnotation(DataSource.List.class).value()));
		}
		if(Context.getTestedClass().isAnnotationPresent(DataSource.class)) {
			datasources.add(Context.getTestedClass().getAnnotation(DataSource.class));
		}
		if(!datasources.isEmpty()) {
			return getDataSourceWithNameByAnnotation(datasources, name);
		} else {
			try {
				return getDataSourceWithNameInContext(name);
			} catch (NoSuchBeanDefinitionException e) {
				return getDataSourceWithNameByProperties(name);
			}
		}
	}

	private static javax.sql.DataSource getDataSourceWithNameByAnnotation(List<DataSource> datasources, String name) {
		for (DataSource dataSource : datasources) {
			if(dataSource.name().equals(name));
				return getDataSourceByAnnotation(dataSource);
		}
		throw new DBTestIllegalUseException("There is no data source named "+name+".");
	}

	private static javax.sql.DataSource getDataSourceByAnnotation(DataSource annotation) {
		String driver = annotation.driver();
		String url = annotation.url();
		String user = annotation.user();
		String password = annotation.password();
		
		DriverManagerDataSource dataSource;
		if(annotation.anonymous())
			dataSource = new DriverManagerDataSource(url);
		else
			dataSource = new DriverManagerDataSource(url, user, password);

		dataSource.setDriverClassName(driver);
		return dataSource;
	}
	
	/**
	 * get the Default DataSource defined in the spring context
	 * @return DataSource
	 */
	public static javax.sql.DataSource getDefaultDataSourceInContext() {
		return Context.getApplicationContext().getBean(javax.sql.DataSource.class);
	}

	/**
	 * get the DataSource in Spring Context with the name of the bean
	 * @param bean name of the bean
	 * @return the DataSource
	 * @throws IOException
	 */
	public static javax.sql.DataSource getDataSourceWithNameInContext(String bean) throws IOException {
		return Context.getApplicationContext().getBean(bean, javax.sql.DataSource.class);
	}

	/**
	 * create a DataSource defined by a properties file
	 * @return DataSource
	 * @throws IOException
	 */
	public static javax.sql.DataSource getDataSourceByProperties() throws IOException {
		return getDataSourceByProperties("data-source.properties");
	}

	/**
	 * create a DataSource defined by a properties file with rthe specified name
	 * @return DataSource
	 * @throws IOException
	 */

	public static javax.sql.DataSource getDataSourceWithNameByProperties(String name) throws IOException {
		return getDataSourceByProperties(name, "data-source.properties");
	}

	public static javax.sql.DataSource getDataSourceByProperties(String filename) throws IOException {
		Properties props = new Properties();
		InputStream in = ClassLoader.getSystemResourceAsStream(filename);
		if(in == null)
			throw new DBTestIllegalUseException("No datasource was found on application context or no file 'data-source.properties' was found on classpath with values 'jdbc.driver', 'jdbc.url', 'jdbc.user', 'jdbc.password' defined");
		props.load(in);
		String driver = props.getProperty("db.driver") != null ? props.getProperty("db.driver") : props.getProperty("jdbc.driver");
		String url = props.getProperty("db.url") != null ? props.getProperty("db.url") : props.getProperty("jdbc.url");
		String user = props.getProperty("db.username") != null ? props.getProperty("db.username") : props.getProperty("jdbc.username");
		String password = props.getProperty("db.password") != null ? props.getProperty("db.password") : props.getProperty("jdbc.password");


		String dialect = props.getProperty("db.dialect") != null ? props.getProperty("db.dialect") : props.getProperty("jdbc.dialect");
		DialectEnum dialectEnum = DialectEnum.getDialect(dialect);
		if(dialectEnum != null)
			mapDialectByDataSourceDeclaredInProperties.put("", dialectEnum);
		DriverManagerDataSource dataSource = new DriverManagerDataSource(url, user, password);
		dataSource.setDriverClassName(driver);
		return dataSource;
	}

	public static javax.sql.DataSource getDataSourceByProperties(String name, String filename) throws IOException {
		Properties props = new Properties();
		InputStream in = ClassLoader.getSystemResourceAsStream(filename);
		if(in == null)
			throw new DBTestIllegalUseException("No datasource was found on application context or no file 'data-source.properties' was found on classpath with values 'jdbc.driver', 'jdbc.url', 'jdbc.user', 'jdbc.password' defined");
		props.load(in);
		String driver = props.getProperty(name+".db.driver") != null ? props.getProperty(name+".db.driver") : props.getProperty(name+".jdbc.driver");
		String url = props.getProperty(name+".db.url") != null ? props.getProperty(name+".db.url") : props.getProperty(name+".jdbc.url");
		String user = props.getProperty(name+".db.username") != null ? props.getProperty(name+".db.username") : props.getProperty(name+".jdbc.username");
		String password = props.getProperty(name+".db.password") != null ? props.getProperty(name+".db.password") : props.getProperty(name+".jdbc.password");
		
		String dialect = props.getProperty(name+".db.dialect") != null ? props.getProperty(name+".db.dialect") : props.getProperty(name+".jdbc.dialect");
		DialectEnum dialectEnum = DialectEnum.getDialect(dialect);
		if(dialectEnum != null)
			mapDialectByDataSourceDeclaredInProperties.put(name, dialectEnum);
		DriverManagerDataSource dataSource = new DriverManagerDataSource(url, user, password);
		dataSource.setDriverClassName(driver);
		return dataSource;
	}

	/**
	 * get the dialect defined for a datasource name
	 * @param dataSourceName
	 * @return
	 */
	public static DialectEnum getDialect(String dataSourceName) {
		if(Context.getTestedClass().isAnnotationPresent(Dialect.List.class) || Context.getTestedClass().isAnnotationPresent(Dialect.class)) {
			List<Dialect> dialects = new ArrayList<Dialect>();
			if(Context.getTestedClass().isAnnotationPresent(Dialect.List.class)) {
				dialects.addAll(Arrays.asList(Context.getTestedClass().getAnnotation(Dialect.List.class).value()));
			}
			if(Context.getTestedClass().isAnnotationPresent(Dialect.class)) {
				dialects.add(Context.getTestedClass().getAnnotation(Dialect.class));
			}
			for (Dialect dialect : dialects) {
				if(dialect.dataSource().equals(dataSourceName))
					return dialect.value();
			}
			return null;
		} else {
			return mapDialectByDataSourceDeclaredInProperties.get(dataSourceName);
		}
	}
}
